//===----------------------------------------------------------------------===//
//
//                         BusTub
//
// buffer_pool_manager_instance.cpp
//
// Identification: src/buffer/buffer_pool_manager.cpp
//
// Copyright (c) 2015-2021, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include "buffer/buffer_pool_manager_instance.h"

#include "common/macros.h"

namespace bustub {

BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, DiskManager *disk_manager,
                                                     LogManager *log_manager)
    : BufferPoolManagerInstance(pool_size, 1, 0, disk_manager, log_manager) {}

BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, uint32_t num_instances, uint32_t instance_index,
                                                     DiskManager *disk_manager, LogManager *log_manager)
    : pool_size_(pool_size),
      num_instances_(num_instances),
      instance_index_(instance_index),
      next_page_id_(instance_index),
      disk_manager_(disk_manager),
      log_manager_(log_manager) {
  BUSTUB_ASSERT(num_instances > 0, "If BPI is not part of a pool, then the pool size should just be 1");
  BUSTUB_ASSERT(
      instance_index < num_instances,
      "BPI index cannot be greater than the number of BPIs in the pool. In non-parallel case, index should just be 1.");
  // We allocate a consecutive memory space for the buffer pool.
  pages_ = new Page[pool_size_];
  replacer_ = new LRUReplacer(pool_size);

  // Initially, every page is in the free list.
  for (size_t i = 0; i < pool_size_; ++i) {
    free_list_.emplace_back(static_cast<int>(i));
  }
}

BufferPoolManagerInstance::~BufferPoolManagerInstance() {
  delete[] pages_;
  delete replacer_;
}

bool BufferPoolManagerInstance::FlushPgImp(page_id_t page_id) {
  // Make sure you call DiskManager::WritePage!
  latch_.lock();
  auto iter = page_table_.find(page_id);
  if(iter == page_table_.end() || page_id == INVALID_PAGE_ID){
    latch_.unlock();
    return false;
  }

  frame_id_t flush_fid = iter->second;
  disk_manager_->WritePage(page_id,pages_[flush_fid].GetData());
  latch_.unlock();
  return true;
}

void BufferPoolManagerInstance::FlushAllPgsImp() {
  // You can do it!
  std::lock_guard<std::mutex> lockGuard(latch_);

  for (auto iter = page_table_.begin(); iter !=page_table_.end(); iter ++) {
    frame_id_t frame_id = iter->second;
    disk_manager_->WritePage(iter->first, pages_[frame_id].GetData());
  }
}

Page *BufferPoolManagerInstance::NewPgImp(page_id_t *page_id) {
  // 0.   Make sure you call AllocatePage!
  // 1.   If all the pages in the buffer pool are pinned, return nullptr.
  // 2.   Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
  // 3.   Update P's metadata, zero out memory and add P to the page table.
  // 4.   Set the page ID output parameter. Return a pointer to P.
  latch_.lock();

  //1.
  bool all_pinned = true;
  for(int i = 0;i < static_cast<int>(pool_size_);++i){
    if(pages_[i].pin_count_ == 0){
      all_pinned = false;
      break;
    }
  }

  if(all_pinned){
    latch_.unlock();
    return nullptr;
  }
  
  //2.
  frame_id_t victim_fid;
  if(!find_replace(&victim_fid)){
    latch_.unlock();
    return nullptr;
  }

  page_id_t new_page_id = AllocatePage();
  Page *victim_page = &pages_[victim_fid];

  page_table_.erase(victim_page->page_id_);
  victim_page->page_id_ = new_page_id;
  victim_page->ResetMemory(); // 不要加这个否则报错？？？？？？
  victim_page->pin_count_=1;
  victim_page->is_dirty_ = false;
  replacer_->Pin(victim_fid);

  page_table_[new_page_id] = victim_fid;
  *page_id = new_page_id;

  //disk_manager_->WritePage(new_page_id,victim_page->GetData());
  latch_.unlock();
  return victim_page;
}


Page *BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) {
  // 1.     Search the page table for the requested page (P).
  // 1.1    If P exists, pin it and return it immediately.
  // 1.2    If P does not exist, find a replacement page (R) from either the free list or the replacer.
  //        Note that pages are always found from the free list first.
  // 2.     If R is dirty, write it back to the disk.
  // 3.     Delete R from the page table and insert P.
  // 4.     Update P's metadata, read in the page content from disk, and then return a pointer to P.
  latch_.lock();
  std::unordered_map<page_id_t,frame_id_t>::iterator it = page_table_.find(page_id);
  //1.1 P exits 
  if(it != page_table_.end()){
    frame_id_t frame_id = it->second;
    Page *page = &pages_[frame_id];
    printf("page data:%s\n", page->GetData());
    page->pin_count_++;  //pin the page
    // if (page_id == 0) {
    //   printf("page 0 count ++:%d\n", page->pin_count_);
    // }
    replacer_->Pin(frame_id); //notify replacer

    latch_.unlock();
    return page;
  }

  //1.2 P not exist，看是否满了，满了需要淘汰页  
  frame_id_t replace_fid;
  if(!find_replace(&replace_fid)){
    latch_.unlock();
    return nullptr;
  }

  Page *replace_page = &pages_[replace_fid];

  // //2.if is dirty,write it back to the disk
  if(replace_page->IsDirty()){
    disk_manager_->WritePage(replace_page->GetPageId(), replace_page->GetData());
  }

  //3.
  page_table_.erase(replace_page->GetPageId());
  //create new map
  //page_id <-----> replace_frame_id
  page_table_[page_id] = replace_fid;

  //4.update replace_page info
  Page *newPage = replace_page;
  newPage->page_id_ = page_id;
  newPage->is_dirty_ = false;
  newPage->pin_count_=1;
  disk_manager_->ReadPage(newPage->GetPageId(),newPage->GetData());
  replacer_->Pin(replace_fid);
  latch_.unlock();

  return newPage;
}

bool BufferPoolManagerInstance::DeletePgImp(page_id_t page_id) {
  // 0.   Make sure you call DeallocatePage!
  // 1.   Search the page table for the requested page (P).
  // 1.   If P does not exist, return true.
  // 2.   If P exists, but has a non-zero pin-count, return false. Someone is using the page.
  // 3.   Otherwise, P can be deleted. Remove P from the page table, reset its metadata and return it to the free list.
  latch_.lock();

  auto iter = page_table_.find(page_id);
  if(iter == page_table_.end()){
    latch_.unlock();
    return true;
  }

  Page *page = &pages_[iter->second];
  if(page->pin_count_ > 0){
    latch_.unlock();
    return false;
  }

  if(page->is_dirty_){
    FlushPgImp(page_id);
  }

  //delete in disk in here
  // DeallocatePage(page_id);
  page_table_.erase(page_id);
  //reset metadata
  page->is_dirty_ = false;
  page->pin_count_ = 0;
  page->page_id_ = INVALID_PAGE_ID;

  //return it to the free lisr
  free_list_.push_back(iter->second);
  latch_.unlock();
  return true;
}

bool BufferPoolManagerInstance::UnpinPgImp(page_id_t page_id, bool is_dirty) { 
  latch_.lock();

  //1.如果page_table中就没有
  auto iter = page_table_.find(page_id);
  if(iter == page_table_.end()){
    latch_.unlock();
    return false;
  }

  //2.找到要被Unpin的page
  frame_id_t unpinned_fid = iter->second;
  Page *unpinned_page = &pages_[unpinned_fid];
  if(is_dirty){
    unpinned_page->is_dirty_ = true;
  }

  //fixme:这里我感觉有问题呀？？？但是就是过了？离谱？
  if (unpinned_page->is_dirty_) {
    disk_manager_->WritePage(page_id, unpinned_page->GetData());
  }

  //if page的pin_count==0则直接return，为什么这里=0不需要重新放入到替换策略的列表中，感觉有问题(但也有可能是已经在里面了)
  if(unpinned_page->pin_count_ == 0){
    latch_.unlock();
    return true;
  }

  unpinned_page->pin_count_--;
  if(unpinned_page->GetPinCount() == 0){
    replacer_->Unpin(unpinned_fid);
  }

  latch_.unlock();
  return true; 
}

bool BufferPoolManagerInstance::find_replace(frame_id_t *frame_id){
  //if free_list not empty then we don't need replace page
  //return directly
  if(!free_list_.empty()){
    *frame_id = free_list_.front();
    free_list_.pop_front();
    return true;
  }
  //else we need to find a replace page
  if(replacer_->Victim(frame_id)){
    // Remove entry from page_table
    page_id_t replace_page_id = -1;
    for(const auto &p : page_table_){
      page_id_t pid = p.first;
      frame_id_t fid = p.second;
      if(fid == *frame_id){
        replace_page_id = pid;
        break;
      }
    }

    if(replace_page_id != -1){
      Page *replace_page = &pages_[*frame_id];

      if(replace_page->is_dirty_){
        char *data = pages_[page_table_[replace_page->page_id_]].data_;
        disk_manager_->WritePage(replace_page->page_id_,data);
        replace_page->pin_count_ = 0;
      }

      page_table_.erase(replace_page->page_id_);
    }

    return true;
  }

  return false;
}


page_id_t BufferPoolManagerInstance::AllocatePage() {
  const page_id_t next_page_id = next_page_id_;
  next_page_id_ += num_instances_;
  ValidatePageId(next_page_id);
  return next_page_id;
}

void BufferPoolManagerInstance::ValidatePageId(const page_id_t page_id) const {
  assert(page_id % num_instances_ == instance_index_);  // allocated pages mod back to this BPI
}

}  // namespace bustub
